﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using HtmlAgilityPack;
using GeneCMS.Models.physical;
using System.IO;
using System.Text;
using SystemsAdmin;
using BTR.Core.Linq;
using BulletOnRails4NET;
using BulletOnRails4NET.Models;
using Suteki.Common.Repositories;
using GeneCMS.Models.Helpers;
using BulletOnRails4NET.Controllers;
using GeneCMS.Models.logical.configuration;

namespace GeneCMS.Models.logical
{
    public class Website
    {
        public String AssetsBasePath { get; set; }
        public String PreviewBasePath { get; set; }
        public String EditingBasePath { get; set; }
        public String WebsiteTemplatePath { get; set; }
        public String WebsiteTemplateFolder { get; set; }
        public String WebsiteProductionPath { get; set; }
        public String InjectCodeFilePath { get; set; }
        public String HtmlToInjectForEditing { get; set; }
        public int WebsiteID { get; set; }

        Dictionary<int, List<Page>> _child_pages;

        Repository<Page> _pages_db;

        public void RenderWebsite()
        {
            _pages_db = new Repository<Page>();
            var pages = from page_rows in _pages_db.GetAll() where page_rows.WebsiteID == WebsiteID 
                        orderby page_rows.ParentPageID, page_rows.Position
                        select page_rows;

            PopulateChildHierarchy(pages);
            PrepareFolder(WebsiteTemplateFolder, PreviewBasePath);
            PrepareFolder(WebsiteTemplateFolder, EditingBasePath);

            CopyAssets(AssetsBasePath, PreviewBasePath);
            CopyAssets(AssetsBasePath, EditingBasePath);

            LoadWebsiteMenus(WebsiteID);

            var first_level_pages = _child_pages[-1];

            //Prepare code to be injected into editing pages
            StreamReader re = File.OpenText(InjectCodeFilePath);
            HtmlToInjectForEditing = re.ReadToEnd();
            re.Close();

            foreach (var page in first_level_pages)
            {
                RenderPage(page, WebsiteTemplatePath, "", 1);
            }
            _pages_db.SubmitChanges();
        }

        /// <summary>
        /// Erases any files previously generated.
        /// </summary>
        /// <param name="template_folder"></param>
        /// <param name="output_folder"></param>
        void PrepareFolder(string template_folder, string output_folder)
        {
            if (GeneConfiguration.GetInstance().ErasePreviewBeforeGenerating && Directory.Exists(output_folder))
            {
                Directory.Delete(output_folder, true);
            }
            Directory.CreateDirectory(output_folder);
            DirectoryInfo dir = new DirectoryInfo(template_folder );
            dir.CopyTo(output_folder, true);
        }

        /// <summary>
        /// Copy assets to preview folder
        /// </summary>
        /// <param name="template_folder"></param>
        /// <param name="output_folder"></param>
        void CopyAssets(string assets_folder, string output_folder)
        {
            //if (Directory.Exists(output_folder))
            //{
            //    Directory.Delete(output_folder, true);
            //}
            //Directory.CreateDirectory(output_folder);
            DirectoryInfo dir = new DirectoryInfo(assets_folder);
            dir.CopyTo(output_folder, true);
        }
        /// <summary>
        /// Puts every page on a tree hierarchy for easy traversal.
        /// </summary>
        /// <param name="pages"></param>
        void PopulateChildHierarchy(IEnumerable<Page> pages)
        {
            _child_pages = new Dictionary<int, List<Page>>();
            int key;
            foreach (var page in pages)
            {
                key = (page.ParentPageID ?? -1);
                if (!_child_pages.Keys.Contains(key))
                {
                    _child_pages.Add(key, new List<Page>());
                }
                _child_pages[key].Add(page);
            }
        }

        /// <summary>
        /// Generates the page file represented by page.
        /// </summary>
        /// <param name="page"></param>
        /// <param name="html_template_file"></param>
        /// <param name="base_url"></param>
        /// <param name="current_page_level"></param>
        public void RenderPage(Page page, string html_template_file, string base_url, int current_page_level)
        {
            if (current_page_level > GeneConfiguration.GetInstance().MaxLevels)
                return;
            if (page.Url.ToString().IndexOf(':') < 0) //only generate the page if they are relative (not just absolute links)
            {
                HtmlDocument doc = new HtmlDocument();
                string xml_file_path = html_template_file;
                doc.Load(xml_file_path);

                //CMSDBDataContext db = new CMSDBDataContext();
                var contents = page.Contents;
                HtmlNode elem_dom;
                //Set title meta
                var title_node = doc.DocumentNode.SelectSingleNode("//title");
                title_node.InnerHtml = page.Title;

                foreach (var content in contents)
                {
                    if (content.AttributeToMatch != null)
                    {
                        var nodes = doc.DocumentNode.SelectNodes("//*[@" + content.AttributeToMatch.ToLower() + "='" + content.ElementMatchCriteria + "']");

                        if (nodes != null)
                        {
                            foreach (var node in nodes)
                            {
                                node.InnerHtml = content.Html;
                            }
                        }
                    }
                    else
                    {
                        elem_dom = doc.GetElementbyId(content.ElementMatchCriteria);
                        if (elem_dom != null)
                        {
                            elem_dom.InnerHtml = content.Html;
                        }
                    }
                }

                //Menu Building step
                HtmlNode menu_node;
                foreach (var menu in _website_menus)
                {
                    if (menu.AttributeToMatch != null)
                    {
                        var nodes = doc.DocumentNode.SelectNodes("//*[@" + menu.AttributeToMatch.ToLower() + "='" + menu.ElementMatchCriteria + "']");

                        if (nodes != null)
                        {
                            foreach (var node in nodes)
                            {
                                if (menu.StartLevel.HasValue)
                                    node.InnerHtml = BuildHtmlMenu(page.Page1, page.ID, menu.EndLevel, current_page_level, "", page.WebsiteID.Value);
                                else
                                    node.InnerHtml = BuildHtmlMenu(null, page.ID, menu.EndLevel, current_page_level, "", page.WebsiteID.Value);
                            }
                        }
                    }
                    else
                    {
                        menu_node = doc.GetElementbyId(menu.ElementMatchCriteria);
                        if (menu_node != null)
                        {
                            if (menu.StartLevel.HasValue)
                                menu_node.InnerHtml = BuildHtmlMenu(page.Page1, page.ID, menu.EndLevel, current_page_level, "", page.WebsiteID.Value);
                            else
                                menu_node.InnerHtml = BuildHtmlMenu(null, page.ID, menu.EndLevel, current_page_level, "", page.WebsiteID.Value);
                        }
                    }
                }


                string xml_out_file_path = PreviewBasePath + base_url + page.Url;
                string folder_out_path = PreviewBasePath + base_url;
                if (!Directory.Exists(folder_out_path))
                {
                    Directory.CreateDirectory(folder_out_path);
                }
                //NO we prefer saving the generated html to the page record itself 
                // with the body element to be extracted by html agility pack when editing contents
                //SaveGeneratedPageToDB(doc, page, base_url + page.Url);
                page.FullUrl = base_url + page.Url;
                // We dont need the generated html to be stored in DB, too slow
                //StringWriter writer = new StringWriter();
                //doc.Save(writer);
                //page.GeneratedHTML = writer.ToString();

                //Process Head Element
                var head_elem = doc.DocumentNode.SelectSingleNode("//head");
                string processed_html = UrlHelper.AddLinkToBase(base_url, head_elem.InnerHtml);
                head_elem.InnerHtml = processed_html;

                //Process Body Element
                var body_elem = doc.DocumentNode.SelectSingleNode("//body");
                processed_html = UrlHelper.AddLinkToBase(base_url, body_elem.InnerHtml);
                body_elem.InnerHtml = processed_html;
                doc.Save(xml_out_file_path);

                //Now output the editing folder
                xml_out_file_path = EditingBasePath + base_url + page.Url;
                folder_out_path = EditingBasePath + base_url;
                if (!Directory.Exists(folder_out_path))
                {
                    Directory.CreateDirectory(folder_out_path);
                }

                string url_image_brsr = UrlHelper.AddPathToRoot( "{site_root}website/assetsbrowse?Type=Link&Levels=" + current_page_level);
                string to_inject = HtmlToInjectForEditing.Replace("${GENECMS-URL-IMAGE-BRWSR}", url_image_brsr);

                body_elem.InnerHtml = UrlHelper.AddPathToRoot(to_inject.Replace("${GENECMS-PAGE-PREVIEW}", body_elem.InnerHtml)) + ContentRendererHelper.AddContentEditsToPage(page.ID, contents);
                doc.Save(xml_out_file_path);

                //Process page's children
                string base_path_for_children = base_url + page.Folder + "/";
                if (_child_pages.Keys.Contains(page.ID))
                {
                    List<Page> children_pages = _child_pages[page.ID];
                    foreach (var child_page in children_pages)
                    {
                        RenderPage(child_page, WebsiteTemplatePath, base_path_for_children, current_page_level + 1);
                    }
                }
            }
        }

        private List<Menu> _website_menus;
        public void LoadWebsiteMenus(int website_id)
        {
            Repository<Menu> menu_data = new Repository<Menu>();
            _website_menus = (from menu_rows in menu_data.GetAll() where
                                menu_rows.WebsiteID == website_id
                              select menu_rows).ToList();
        }

        /// <summary>
        /// Generates an HTML menu. The item generated depend on the current
        /// menu level and the maximum level allowed.
        /// </summary>
        /// <param name="base_page">The children pages of this page will be used for the items in the menu. If null, the pages are taken from the database by querying the top level pages.</param>
        /// <param name="curr_page_id"></param>
        /// <param name="max_level">How many levels deep the menu should be</param>
        /// <param name="curr_menu_level">Use to modify the links to make them refer to the correct base url</param>
        /// <param name="url_prefix"></param>
        /// <returns></returns>
        public string BuildHtmlMenu(Page base_page, int curr_page_id,
            int? max_level, int curr_menu_level, string url_prefix, int website_id)
        {
            List<Page> pages = new List<Page>() ;
            if (base_page == null)
            {
                CMSDBDataContext db = new CMSDBDataContext();
                pages = (from pages_row in db.Pages
                         where pages_row.ParentPageID == null && pages_row.WebsiteID == website_id
                        orderby pages_row.Position
                         select pages_row).ToList<Page>();
                //Add url prefix to navigate back to root based on current page level
                for (int num_parents = 1; num_parents < curr_menu_level; num_parents++)
                {
                    url_prefix += "../";
                }
            }
            else
            {
                pages = base_page.Pages.ToList<Page>();
            }

            StringBuilder html = new StringBuilder();
            bool has_pages = false;


            foreach (var page in pages)
            {
                if (!has_pages)
                {
                    html.AppendLine("<ul>");
                    has_pages = true;
                }
                if (curr_page_id == page.ID)
                {
                    html.Append("<li><A class=\"sel\" href=\"");
                }
                else
                {
                    html.Append("<li><A href=\"");
                }
                if (page.Url.ToString().IndexOf(':') >= 0)
                {
                    //Is an absolute url
                    html.Append(page.Url);
                    html.Append("\" target=\"_blank\"><SPAN></SPAN>");
                }
                else
                {
                    html.AppendLine(url_prefix + page.Url);
                    html.Append("\" ><SPAN></SPAN>");
                }
                html.AppendLine(page.Title);
                html.AppendLine("</A>");

                if (!max_level.HasValue || (max_level.HasValue && max_level > curr_menu_level))
                {
                    //Build sub-menu if menu's setting allows it
                    string new_prefix = url_prefix;
                    if (page.Folder != null)
                        new_prefix += page.Folder + "/";
                    html.AppendLine(BuildHtmlMenu(page, curr_page_id, max_level, curr_menu_level + 1, new_prefix, website_id));
                }
                html.AppendLine("</LI>");

            }
            if (has_pages)
            {
                html.AppendLine("</ul>");
                has_pages = false;
            }
            return html.ToString();
        }

        public void PublishWebsite()
        {
            RenderWebsite();
            if (WebsiteProductionPath.IsBlank())
            {
                //TODO: implement ftp
            }
            else
            {
                //Generate the website wihout editing
                if (!Directory.Exists(WebsiteProductionPath))
                {
                    Directory.CreateDirectory(WebsiteProductionPath);
                }
                DirectoryInfo dir = new DirectoryInfo(PreviewBasePath);
                dir.CopyTo(WebsiteProductionPath, true);
            }
        }
    }
}
